package primitives;

import light.Light;
import colour.Colour;
import ray.Ray;
import surface.Surface;
import vector.Vector;

/**
 * Contain the element for defining a Plane. Provide the method for operation on
 * them.
 * 
 * @author Yuting Wu
 * 
 */

public class Plane extends AbstractShape {

	// Initial parameter for constructing plane
	private Vector vNormal;
	// Initial parameter for constructing plane
	private double distance;
	// direction vector of intersection point
	private Vector vHitPoint;
	// position vector of intersection point
	private Vector pHitPoint;
	// distance from the origin of the coordinate system to the plane
	private double disPlane;
	private double cosRayNormal;
	// Distance from intersection point
	private double disHitPoint;

	/**
	 * Constructor of Plane. Three parameters is needed: vNormal, pOnPlane and
	 * surface
	 * 
	 * @param vNormal
	 * @param pOnPlane
	 * @param surface
	 */
	public Plane(Vector vNormal, Vector pOnPlane, Surface surface) {
		super(surface);
		this.vNormal = vNormal.normalise();
		this.distance = pOnPlane.dotProduct(vNormal);

	}

	/**
	 * Constructor of Plane. Three parameters is needed: vNormal, distance and
	 * surface
	 * 
	 * @param vNormal
	 * @param distance
	 * @param surface
	 */
	public Plane(Vector vNormal, double distance, Surface surface) {
		super(surface);
		this.vNormal = vNormal.normalise();
		this.distance = distance;

	}

	/**
	 * Getter method. It will be used for getting parameter vNormal. This method
	 * also will be used for testing method.
	 * 
	 * @return the vPlaneRightAngle
	 */
	public Vector getNormal() {
		return vNormal;
	}

	/**
	 * Getter method. It will be used for getting parameter distance. This
	 * method also will be used for testing method.
	 * 
	 * @return the distance
	 */
	public double getDistance() {
		return distance;
	}

	/**
	 * Get the colour on the plane surface of the hitpoint. If there is no
	 * information about colour value in xml file, it will be set to (0,0,0)
	 * 
	 * @return the result colour
	 */
	@Override
	public Colour getColorAt(Vector pHitPoint, Vector vHitPoint) {
		Colour result;
		if (surface == null) {
			result = new Colour(0, 0, 0);
		} else {
			result = this.surface.getColour();
		}
		return result;
	}

	/**
	 * Get the default diffuse value. If there is no information about diffuse
	 * value in xml file, it will be set to 1.0
	 * 
	 * @return the default diffuse value.
	 */
	@Override
	public double getDiffuse() {
		// set the default
		double diffuse;
		if (surface != null) {
			diffuse = this.surface.getDiffuse();
		} else {
			diffuse = 1.0;
		}

		return diffuse;
	}

	/**
	 * Get the default reflect value. If there is no information about reflect
	 * value in xml file, it will be set to 0.0
	 * 
	 * @return the default reflect value.
	 */
	@Override
	public double getReflect() {
		double reflect;
		if (surface != null) {
			reflect = this.surface.getReflect();
		} else {
			reflect = 0.0;
		}

		return reflect;

	}

	/**
	 * Override toString method.
	 * 
	 * @return all the information from a Triangle
	 */
	public String toString() {
		return "Plane:Normal: " + vNormal + ",distance " + distance
				+ ",suface " + surface;
	}

	/**
	 * To judge whether it is hit the plane
	 * 
	 * @param ray
	 *            ray which is used for calculation
	 * 
	 * @return boolean to show if hit
	 */
	@Override
	public boolean calMutiIntersection(Ray raylist, Boolean ifSimple) {
		disHitPoint = -1;
		vNormal = getNormal();
		disPlane = getDistance();
		Boolean ifTure = false;
		// if the cosRayNormal is equal to zero, no hitpoint
		cosRayNormal = vNormal.dotProduct(raylist.getDesiredRay());
		if (cosRayNormal != 0) {
			this.calDistance(raylist);
			if (disHitPoint >= 0) {
				ifTure = true;
			}
		}
		return ifTure;
	}

	/**
	 * Getter method. Get distance from hit point
	 * 
	 * @return the distance from hit point
	 */
	@Override
	public double getDisHitPointIndex() {
		return disHitPoint;
	}

	/**
	 * Getter method. get the position vector of hit point
	 * 
	 * @return the position vector of hit point
	 */
	@Override
	public Vector getPositionHitPointIndex() {
		return pHitPoint;
	}

	/**
	 * Getter method. get the direction vector of hit point
	 * 
	 * @return the direction vector of hit point
	 */
	@Override
	public Vector getVectorHitPointIndex() {
		return vHitPoint;
	}

	/**
	 * This method is mainly used for Sphere.
	 */
	@Override
	public boolean isInsideOrOutside() {
		return false;
	}

	/**
	 * Calculate the postion and vector of the hitpoint
	 */
	@Override
	public void runFurtherCalculation(Ray rayInput) {
		if (cosRayNormal != 0 && disHitPoint >= 0) {
			pHitPoint = this.calHitPoint(rayInput);
			vHitPoint = this.calVectorIntersectionPoint();
		}

	}

	/**
	 * calculate the intensity of the light through the angle of light and
	 * normal of the hitpoint
	 */
	@Override
	public double lightIntensityCalculation(Light singleLight,
			Vector pHitPoint, Vector vHitPoint) {
		Vector s0 = pHitPoint.add(vHitPoint.multiply(0.0001));
		Vector sd = singleLight.getLight().subtract(s0).normalise();
		return Math.abs(vHitPoint.dotProduct(sd)) * this.getDiffuse();
	}

	/**
	 * if the distance is smaller than zero, no hitpoint
	 * 
	 * @param rayInput
	 */
	private void calDistance(final Ray rayInput) {
		final double v0Val = disPlane
				- vNormal.dotProduct(rayInput.getOrigin());
		disHitPoint = v0Val / cosRayNormal;
	}

	/**
	 * this is a method for calculate the position of the hitpoint
	 * 
	 * @param rayInput
	 * @return position of hitpoint as vector
	 */
	private Vector calHitPoint(final Ray rayInput) {
		final Vector pHitPoint = rayInput.getOrigin().add(
				rayInput.getDesiredRay().multiply(disHitPoint));
		return pHitPoint;

	}

	/**
	 * this is a method for calculate the normal vector of the hitpoint
	 * 
	 * @return normal vector for the hitpoint as vector
	 */
	private Vector calVectorIntersectionPoint() {
		final Vector vHitPoint;
		if (cosRayNormal < 0) {
			vHitPoint = vNormal;
		} else {
			vHitPoint = vNormal.multiply(-1);
		}
		return vHitPoint;
	}
}
